// vPresent is an immersive presentation creation and display application.
// Copyright (C) 2007-2009 by Priority 5 Holdings, Inc.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

#include <cstdlib>
#include <string>
#include <sstream>
#include <stdexcept>
#include <boost/program_options.hpp>

#include <OpenSG/OSGConfig.h>
#include <OpenSG/OSGBaseTypes.h>
#include <OpenSG/OSGBaseInitFunctions.h>
#include <OpenSG/OSGConnectionFactory.h>
#include <OpenSG/OSGPointConnection.h>

#include "Window.h"
#include "RenderServer.h"


namespace po = boost::program_options;

/**
 * A simple guard around an OpenSG point or group connection object
 * (subclasses of OSG::Connection). This exists so that it is easy to have a
 * heap-allocated socket that is cleaned up completely if an exception is
 * thrown during network communication.
 */
template<typename SocketType>
class SocketGuard
{
public:
   SocketGuard(SocketType* sock)
      : mSocket(sock)
   {
      /* Do nothing. */ ;
   }

   ~SocketGuard()
   {
      mSocket->disconnect();
      delete mSocket;
   }

   /** @name Smart Pointer Interface */
   //@{
   SocketType* operator->()
   {
      return mSocket;
   }

   SocketType& operator*()
   {
      return *mSocket;
   }
   //@}

private:
   SocketType* mSocket;
};

struct Configuration
{
   Configuration()
      : numPipes(1)
      , fullScreen(false)
      , alwaysOnTop(false)
      , stereo(false)
      , hasBorder(false)
      , width(300)
      , height(300)
      , xPos(0)
      , yPos(0)
   {
      /* Do nothing. */ ;
   }

   // NOTE: We have to use OSG::Int32 for all integers that are received
   // because Python does not have unsigned integers.

   std::string name;
   std::string connectionType;
   OSG::Int32 servicePort;
   std::string serviceGroup;
   OSG::Int32 numPipes;
   std::string screenID;
   bool fullScreen;
   bool alwaysOnTop;
   bool stereo;
   bool hasBorder;
   OSG::Int32 width;
   OSG::Int32 height;
   OSG::Int32 xPos;
   OSG::Int32 yPos;
};

void configure(const std::string& address, Configuration& cfg)
{
   bool done_configuring(false);

   while ( ! done_configuring )
   {
      SocketGuard<OSG::PointConnection> config_conn(
         OSG::ConnectionFactory::the()->createPoint("StreamSock")
      );

      config_conn->bind(address);

      const OSG::Connection::Channel channel = config_conn->acceptPoint();

      // A handy thing about OSG::Connection::getValue(std::string&) is that
      // it figures out on its own the length of the string coming over the
      // wire. This requires that OSG::Connection::putValue() be used on the
      // other side, of course.
      config_conn->getValue(cfg.name);
      config_conn->getValue(cfg.connectionType);

      if ( cfg.connectionType == "Multicast" )
      {
         config_conn->getValue(cfg.serviceGroup);
         config_conn->getValue(cfg.servicePort);
      }

      config_conn->getValue(cfg.numPipes);

      if ( cfg.numPipes > 1 )
      {
         std::cerr << "WARNING: Multi-pipe configurations are unsupported!"
                   << std::endl;
      }
      else if ( cfg.numPipes < 0 )
      {
         std::ostringstream msg_stream;
         msg_stream << "Received invalid number of pipes: " << cfg.numPipes;
         throw std::runtime_error(msg_stream.str().c_str());
      }

      // TODO: Loop over num_pipes to create a vector of vp::Window objects.
      config_conn->getValue(cfg.screenID);
      config_conn->getValue(cfg.fullScreen);
      config_conn->getValue(cfg.alwaysOnTop);
      config_conn->getValue(cfg.stereo);
      config_conn->getValue(cfg.hasBorder);
      config_conn->getValue(cfg.width);
      config_conn->getValue(cfg.height);
      config_conn->getValue(cfg.xPos);
      config_conn->getValue(cfg.yPos);

      if ( cfg.width <= 0 )
      {
         std::ostringstream msg_stream;
         msg_stream << "Received invalid window width: " << cfg.width;
         throw std::runtime_error(msg_stream.str().c_str());
      }

      if ( cfg.height <= 0 )
      {
         std::ostringstream msg_stream;
         msg_stream << "Received invalid window height: " << cfg.height;
         throw std::runtime_error(msg_stream.str().c_str());
      }

      config_conn->getValue(done_configuring);

      // config_conn goes out of scope here, and thus the connection is
      // closed. If done_configuring is still false, then a new connection is
      // opened to receive fresh configuration information later.
   }

   // At this point, we have received the configuration information that we
   // need unless an exception was thrown. If an exception was thrown, then
   // the socket connection with the vPresent GUI gets closed automatically
   // when config_conn goes out of scope.
}

int main(int argc, char** argv)
{
   bool exit_on_error(false);
   std::string address(":10145");

   try
   {
      po::options_description prog_opts("Options");

      prog_opts.add_options()
         ("help,h", "Print help message")
         ("exit-on-error,e", "Exit on error")
         ("address,a", po::value<std::string>()->default_value(address),
          "Address on which we wait for an incoming vPresent GUI connection.")
         ;

      po::variables_map vm;
      po::store(po::command_line_parser(argc, argv).options(prog_opts).run(),
                vm);
      po::notify(vm);

      if ( vm.count("help") > 0 )
      {
         std::cout << prog_opts << std::endl;
         return 0;
      }

      if ( vm.count("address") > 0 )
      {
         address = vm["address"].as<std::string>();
      }

      exit_on_error = vm.count("exit-on-error") > 0;
   }
   catch (std::exception& ex)
   {
      std::cerr << ex.what() << std::endl;
      return EXIT_FAILURE;
   }

   int status(EXIT_SUCCESS);

   try
   {
      OSG::osgInit(argc, argv);

      Configuration cfg;

      // This blocks until configuration is complete. If no exception is
      // thrown, then we are set to proceed with the standard OpenSG cluster
      // communication.
      configure(address, cfg);

      // TODO: Make use of cfg.hasBorder.
      // TODO: Make use of cfg.screenID.
      vp::WindowPtr window = vp::Window::create()->init(argc, argv,
                                                        cfg.fullScreen,
                                                        cfg.alwaysOnTop,
                                                        cfg.stereo, cfg.name,
                                                        cfg.xPos, cfg.yPos,
                                                        cfg.width, cfg.height);
      vp::RenderServer server(cfg.name, window, cfg.connectionType, address,
                              cfg.servicePort, cfg.serviceGroup,
                              exit_on_error);
      server.run();
   }
   catch (std::exception& ex)
   {
      std::cerr << ex.what() << std::endl;
      status = EXIT_FAILURE;
   }

   OSG::osgExit();

   return status;
}
